package se

import (
	"embed"
	"io/ioutil"
	"os"
	"sort"
	"time"

	"gitee.com/xuender/oils/execs"
	"gitee.com/xuender/oils/gui"
	"gitee.com/xuender/oils/level"
	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/slices"
	"gitee.com/xuender/se/pb"
	"github.com/fatih/color"
	"github.com/syndtr/goleveldb/leveldb"
)

// nolint: gochecknoglobals
//go:embed www
var www embed.FS

// App 应用.
type App struct {
	ss  *Service
	gs  gui.GUI
	ctx *Context
}

// NewApp 新建应用.
func NewApp(
	ss *Service,
	gs gui.GUI,
	ctx *Context,
) *App {
	return &App{
		ss:  ss,
		gs:  gs,
		ctx: ctx,
	}
}

// NewDB 新建数据库.
func NewDB(ctx *Context) level.DB {
	db, err := leveldb.OpenFile(ctx.DataBasePath, nil)
	logs.Panic(err)

	return db
}

// Close 关闭.
func (p *App) Close() error {
	_ = p.gs.Close()

	return p.ss.Close()
}

// Index 索引文件或目录.
func (p *App) Index(files []string) {
	logs.Debug("索引文件或目录")

	for _, file := range files {
		p.ss.Index(file)
	}
}

// Min 缩小索引.
func (p *App) Min() {
	logs.Debug("缩小索引")
	p.ss.Min()
}

// File 文件详情.
func (p *App) File(file string) {
	fi := p.ss.FileInfo(file)

	if fi.IsDir {
		color.White("目录名: %s", fi.Name)

		return
	}

	var w int64 = 1e9
	t := time.Unix(fi.ModTime/w, fi.ModTime%w)

	cw := color.New(color.FgWhite)
	c := color.New(color.FgCyan)

	cw.Println("文件名:", fi.Name)
	cw.Println("文件尺寸:", FormatFileSize(fi.Size))
	cw.Println("最后修改时间:", t.Format("2006-01-02 15:04:05"))
	cw.Println("索引大小:", FormatFileSize(int64(fi.IndexSize)))

	cw.Println("包含的词汇:")

	for i, w := range fi.Words {
		if i > 0 {
			cw.Print(", ")
		}

		c.Print(w)
	}

	cw.Println("\n词汇合计:", fi.WordCount)
}

// Word 词详情.
func (p *App) Word(word string) {
	wi := p.ss.WordInfo(word)
	cw := color.New(color.FgWhite)
	c := color.New(color.FgCyan)

	cw.Println("ID:", wi.Id)
	cw.Println("词:", wi.Word)
	cw.Println("索引大小:", FormatFileSize(int64(wi.IndexSize)))

	for _, f := range wi.Files {
		cw.Printf("ID:%d\t", f.Id)
		cw.Print(FormatFileSize(f.Size))
		cw.Print("\t")
		c.Println(f.Name)
	}

	cw.Println("关联的文件:", wi.Count)
}

// Files 索引文件列表.
func (p *App) Files() {
	logs.Debug("索引文件列表")
	color.White("ID\tSize\tWords\tModTime\t\t\tName")

	for _, f := range p.ss.fs.FilesByStart(p.ctx.Start) {
		var w int64 = 1e9
		t := time.Unix(f.ModTime/w, f.ModTime%w)

		if f.IsDir {
			color.Cyan("%d\t目录\t\t%s\t%s", f.Id, t.Format("2006-01-02 15:04:05"), f.Name)

			continue
		}

		color.Cyan("%d\t%s\t%d\t%s\t%s", f.Id, FormatFileSize(f.Size), f.WordCount, t.Format("2006-01-02 15:04:05"), f.Name)
	}
}

// Search 搜索.
func (p *App) Search(words []string) {
	hit := color.New(color.FgRed).Add(color.Underline)
	base := color.New(color.FgWhite)
	file := color.New(color.FgCyan)

	for _, f := range p.ss.Search(words) {
		base.Print("\n文件:")
		file.Println(f.Name)

		for _, h := range f.Hits {
			base.Printf("%d: ", h.Line)

			rs := []rune(h.Text)
			s := 0

			for _, w := range mapToSlice(h.Words) {
				base.Print(string(rs[s:w[0]]))
				hit.Print(string(rs[w[0]:w[1]]))
				s = w[1]
			}

			base.Println(string(rs[s:]))
		}
	}
}

// 将词的位置map转换成slice.
func mapToSlice(words map[uint32]uint32) [][2]int {
	ret := [][2]int{}
	for k, v := range words {
		ret = append(ret, [2]int{int(k), int(v)})
	}

	sort.Slice(ret, func(i, j int) bool {
		if ret[i][0] == ret[j][0] {
			return ret[i][1] < ret[j][1]
		}

		return ret[i][0] < ret[j][0]
	})

	return ret
}

// Words 词列表.
func (p *App) Words() {
	logs.Debug("词列表")
	color.White("ID\tCount\tWord")

	for _, w := range p.ss.WordList(p.ctx.Start) {
		color.Red("%d\t%d\t%s", w.Id, w.Count, w.Word)
	}
}

// Reindex 重新索引.
func (p *App) Reindex() {
	logs.Info("重新索引")
	p.ss.Reindex()
}

// Config 显示配置.
func (p *App) getConfig() *pb.Config {
	return p.ss.config
}

func (p *App) getFiles(start string) []*pb.File {
	var w int64 = 1e9

	ret := p.ss.fs.FilesByStart(start)
	for i, f := range ret {
		t := time.Unix(f.ModTime/w, f.ModTime%w)
		ret[i].FormatSize = FormatFileSize(f.Size)
		ret[i].FormatTime = t.Format("2006-01-02 15:04:05")
	}

	logs.Debug(p.ctx)
	logs.Debug(p.ss.config)

	return ret
}

func (p *App) search(text string) []*pb.File {
	return p.ss.Search([]string{text})
}

// Pwd 当前目录.
func Pwd() string {
	pwd, _ := os.Getwd()

	return pwd
}

// ls 显示目录文件.
func (p *App) ls(dir string) []*pb.File {
	ret := []*pb.File{}

	ignore := slices.Strings(p.ss.config.Ignore)

	fileInfoList, err := ioutil.ReadDir(dir)
	if err != nil {
		return ret
	}

	for _, f := range fileInfoList {
		if !f.IsDir() {
			continue
		}

		if ignore.Has(f.Name()) {
			continue
		}

		ret = append(ret, &pb.File{
			Name:       f.Name(),
			FormatTime: f.ModTime().Format("2006-01-02 15:04:05"),
		})
	}

	return ret
}

// Run 运行.
func (p *App) Run() {
	p.gs.FS("www", www)
	// 配置
	p.gs.Bind("getConfig", p.getConfig)
	p.gs.Bind("setConfig", p.ss.SetConfig)
	// 文件
	p.gs.Bind("getFiles", p.getFiles)
	// 词组
	p.gs.Bind("getWords", p.ss.WordList)
	// 搜索
	p.gs.Bind("search", p.search)
	// 文件
	p.gs.Bind("openFile", execs.Open)
	p.gs.Bind("showFile", execs.Show)
	p.gs.Bind("delFile", p.ss.fs.Delete)
	p.gs.Bind("delDir", p.ss.fs.DeleteDir)
	// 目录相关
	p.gs.Bind("pwd", Pwd)
	p.gs.Bind("ls", p.ls)
	// 索引
	p.gs.Bind("index", p.ss.Index)
	p.gs.Bind("indexes", p.ss.Indexes)
	p.gs.Bind("reindex", p.ss.Reindex)
	p.gs.Bind("min", p.ss.Min)

	_ = p.gs.Open("/", "个人搜索引擎", int(p.ss.config.Width), int(p.ss.config.Height))

	p.gs.Run()
}
